home *** CD-ROM | disk | FTP | other *** search
Wrap
/* BBC Radio widget by Hawkman. http://www.phantomgorilla.com/ All suggestions and bug reports to hawkman@phantomgorilla.com please ******************************************************************************* LICENCE Free to use, free to look at and learn from, free to pinch small chunks of code from for use in your own projects. However, you may NOT redistribute this widget, or a port or modified version of it, without obtaining permission from the author (via email, hawkman@phantomgorilla.com) and including credit to him in the finished project (a small notice in the widget's javascript/html files is all that's necessary; nothing visible on the widget). This software is provided as-is, with no guarantee. The author can accept no responsibility for any consequences of its use. ******************************************************************************* ABOUT THE WIDGET - Basic Overview This widget uses Real Player for the audio. Preferences are written upon the selection of an item in a pop-up menu, then this is compared to a list of stations to find the stream url. A function then writes html to the front of the widget to embed the realplayer plugin with the correct url, and changes the image source to reflect which station is playing. - Credits Basic Javascript routines taken out of Apple's sample Goodbye World widget. A small section of crash-preventing code is taken from Duncan Ponting's radio widget (http://www.bbc.co.uk/radio/prototypes/listenlive/) at his suggestion - cheers. Also taken from that widget is the idea to crawl the broswer plugins to find RealPlayer. Version string .plist lookup taken from Joshua Emmons' blank foo.wdgt (http://www.skia.net/). Study of his example AppleAnimator use was also extremely helpful. Some button-like routines adapted from Apple's GenericButton class. Additional skills were obtained by studying Apple's sample files and the Mac OS X bundled widgets, however no actual code was used from these sources other than that mentioned already. ******************************************************************************/ /*******************************/ // GLOBAL VARIABLES & SETTINGS /*******************************/ // Sets up a flag to say whether the "What's On?" link should be showing // (we deliberately don't trigger its hiding on mouseout under certain // circumstances, setting this variable to false instead to allow us to hide // it at a later point). If that's not too clear, try checking the functions. var shouldShowWhatsOn=false; // Sets up a global thingie for the "What's On?" fading animation var whatsOnAnimator = null; // and one for the status animation (which is much quicker) var statusAnimator = null; // sets up a variable to store the XML from checkForUpdate(), also to let // us know if a request is currently in progress var externalXmlRequest = null; // variable to store the current version number from Info.plist; only set on // load, but referred to on multiple occasions var currentVersion; // holds the version number of the latest version available; initially taken // from preferences (if there), it's updated every 2 days by checkForUpdate() var latestVersion = widget.preferenceForKey("latestversion"); // get the last time we checked for an update var then = widget.preferenceForKey("lastupdatecheck"); // all involved with the button handlers such as theMouseDownHandler // // this one stores whether the mouse button is down var theCurrentMouseDownButton = null; // the button which was clicked var wasClickedElement = null; // the element to swap images into var isElementToChange = null; // the starting position of the left side of the volume knob var knobStartLeft = null; // the cursor's starting x-coordinate; used with the slider var cursorStartX = null; // left boundary of the slider var leftBound = 73; // right boundary of the slider var rightBound = 106; // stors the station selected immediately before, and immediately after, showing // the rear - a function compares these to see if it's changed var preferenceBefore = null; var preferenceAfter = null; // used to temporarily store event settings if we use the showStatus() function, // as we can't pass these directly from it to hideStatus() via setTimeout(). var globalStatusEvent; /*****************************************************/ // SETUP FUNCTIONS, SAVING AND RETRIEVING PREFERENCES /*****************************************************/ // onLoad() is run when the body loads. It checks to see if there is a // preference for this widget and if so, applies the preference to the widget. function onLoad() { if(window.widget) // always check to make sure that you are running in Dashboard { // draw the info button, using AppleClasses new AppleInfoButton (document.getElementById('info'), document.getElementById('front'), 'black', 'black', showPrefs); // draw the Done button for the back - again, AppleClasses new AppleGlassButton(document.getElementById('done'), 'Done', hidePrefs); // adds listeners to the "mouse_tracker" span, which holds the logo, // so that the "What's On?" link fades in and out on mouseover/out. // We set that here instead of hardcoding them into the html as it // avoids a (rare) issue where they might not fire properly if the // user left Dashboard while the widget was loading. document.getElementById("mouse_tracker").addEventListener("mouseover", whatsOnOver, true) document.getElementById("mouse_tracker").addEventListener("mouseout", whatsOnOut, true) // gets and shows the correct station image changeImage(); // sets the volume slider to the correct position restoreVolume(); // the next section reads the preferences and ensures that the correct // option is selected in the popup menu to the rear on load var stationPopup = document.getElementById("station"); // checks to see which option number in the popup was selected last time var optionToSelect = widget.preferenceForKey("selection"); // The preference was read as a string, not as a number; if we try to // take 1 from it(next step) already we hit problems. However, // multiplying by 1 turns a numerical string into a number, so we can do // math on it. Genius. optionToSelectNumber=optionToSelect*1; // takes one away; for some reason the numbering system is different // for setting the option to that when reading it! optionToSelectNumber=optionToSelectNumber-1; // we use optionToSelect (the string version) to check that it has // length (ie. that a preference exists) if (optionToSelect && (optionToSelect.length > 0)) { // and if so, we select the relevant option stationPopup.options[optionToSelectNumber].selected = true; } // if there's no match, default to radio 1, as with all settings else { stationPopup.options[0].selected = true } } // calls fillVersion() to fill in the version number on the rear fillVersion(); // check for an update to the widget checkForUpdate(); // if a later version is available if (latestVersion>currentVersion) { // show a status message to alert the user showStatus('', 'An update is available! Please check the reverse of the widget.', 10); } } // called from onLoad() to fill in the author & version information on the rear; // it grabs them from Info.plist function fillVersion() { // start a new XMLHttpRequest var internalXmlRequest = new XMLHttpRequest(); // setup the destination of the request (Info.plist) internalXmlRequest.open("GET", "Info.plist", false); // send nothing internalXmlRequest.send(null); // get all the "key" tags, set them to an array(?) var keys = internalXmlRequest.responseXML.getElementsByTagName("key"); // set the variable to 0.0, so we have a value just in case this fails currentVersion = "0.0"; // set up a loop, to go through every key tag for(i=0; i<keys.length; i++) { // if we hit a match for CFBundleVersion in the data of the "first // child" of any of the keys (XML navigation terminology is rather // confusing) if("CFBundleVersion" == keys[i].firstChild.data) { // if we hit CFBundleVersion, we know where the actual version // number will be inrelation to it (assuming the .plist is valid) // so we can navigate to it - again, confusing-looking, no? // Anyway, we set it to the global varible, so we can refer to it // in other functions currentVersion = keys[i].nextSibling.nextSibling.firstChild.data; // break the loop, we got a match so we're done break; } } // uses the newly-set variable to fill in the version number, along with // the rest of the information, into the span with id="author" document.getElementById("author").innerText = "BBC Radio v"+currentVersion+" by Hawkman"; } // checks to see what the latest version is. Every two days it looks online at // my website, where the latest version will be published; in between it uses // a local cache of the last known value instead. function checkForUpdate() { // get the current date var now = (new Date).getTime(); // if we haven't checked for an update, or if it's been 2 days. Used to only // allow this if an update hadn't been detected previously; however, this // doesn't allow for human error. I want to be able to pull new versions if // they're buggy and un-trigger the update check! if (!then || !latestVersion || (now-then)>172800000) { // record the time that we're checking, both to preferences and our // global variable "then", so that the widget won't check for another // 2 days widget.setPreferenceForKey(now,"lastupdatecheck"); // no, it's not time travel; it's two confusingly-named variables! then = now; // cancel any update request in progress - there damn well shouldn't be // one, if everything's working! if (externalXmlRequest != null) { externalXmlRequest.abort(); externalXmlRequest = null; } // set up the request externalXmlRequest = new XMLHttpRequest(); // when we've got the data, this calls searchResponseForLatestVersion() // to handle it, passing the data to it externalXmlRequest.onload = function(e) {searchResponseForLatestVersion(e, externalXmlRequest);} // gives the url to query, amongst other things; if you want to see // how the other end works, visit that URL in your browser and // view the source externalXmlRequest.open("GET", "http://www.phantomgorilla.com/update/version.xml", false); // make sure we don't get a cached version of the page externalXmlRequest.setRequestHeader("Cache-Control", "no-cache"); // don't send anything externalXmlRequest.send(null); } // now we've got the latest version information, we have to check to see if // there's actually an update, or if this version is current. // if the latest version available is greater than the current version: if (latestVersion>currentVersion) { // hide the Phantom Gorilla graphic in the lower left document.getElementById("phantom").style.display="none" // show the "update" graphic instead document.getElementById("update").style.display="block" } // otherwise, do the reverse (in case the update availability has reverted // while the widget is open; unlikely, but possible else { document.getElementById("update").style.display="none" document.getElementById("phantom").style.display="block" } } // searches the nodes in the page for the one that relates to this product, then // gets the data from it - the latest version number :) function searchResponseForLatestVersion(e, request) { // set the externalXmlRequest to null, so we know no request is in progress externalXmlRequest = null; // if there's an answer with XML if (request.responseXML) { // sets up a variable to use var theChild; // searches through the nodes in order for (theChild=request.responseXML.firstChild; theChild!=null; theChild=theChild.nextSibling) { // if the node is called "BBCRadio" - that's the one relating to us! if (theChild.nodeName == 'BBCRadio') { // get its data; this will be the version number. Set that to // latestVersion, which is a global variable. latestVersion = theChild.firstChild.data; } } // stores the latest version information we just got as a preference; // even though latestVersion is a global variable, we save this now so // that on next load (when it will have cleared) we'll still know what // the latest version is without checking online (till 2 days are up) widget.setPreferenceForKey(latestVersion, "latestversion"); } } // Originally changestation(), but it evolved into a behemoth prefs function. // setPrefs() is called whenever a station is chosen from the popup menu. // It saves the location of the image and the stream url of the corresponding // station and then calls functions which change the image and the audio stream // on the front of the widget. function setPrefs(elem) { // defines a variable var selectedOption = null; // sets selectedOption to the number of the selection selectedOption=elem.options[elem.selectedIndex].value; // check we're in Dashboard; I'm not as consistent with this as I should be, // but we're setting a huge load of preferences here so it's best to be sure if(window.widget) { // save the numbered selection as a preference (so we can set it again // quickly on next launch) widget.setPreferenceForKey(selectedOption,"selection"); // find out which option was chosen switch(parseInt(elem.options[elem.selectedIndex].value)) { // if the first option (radio 1) was chosen case 1: // set the preference for the station logo location widget.setPreferenceForKey("images/r1.gif","logo"); // then for service ID (BBC defined, used to get schedule) widget.setPreferenceForKey("49697","ID"); // then for "band", used to display extra information (such as // UK or international versions of the station) in top left widget.setPreferenceForKey("","band"); // then for the stream url widget.setPreferenceForKey("http://www.bbc.co.uk/radio1/realaudio/media/r1live.rpm","url"); // now change the image on the front as we just changed the // location; it needs to be called immediately so that it's // changed by the time we flip back to the front changeImage(); // break the loop, as we've found the scenario we wanted break; // do the same for the second option (radio 2) case 2: widget.setPreferenceForKey("images/r2.gif","logo"); widget.setPreferenceForKey("49698","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/radio2/realmedia/fmg2.rpm","url"); changeImage(); break; // option 3, etc etc case 3: widget.setPreferenceForKey("images/r3.gif","logo"); widget.setPreferenceForKey("49699","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/radio3/ram/r3g2.rpm","url"); changeImage(); break; case 4: widget.setPreferenceForKey("images/r4.gif","logo"); widget.setPreferenceForKey("49700","ID"); widget.setPreferenceForKey("FM","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/radio4/realplayer/media/fmg2.rpm","url"); changeImage(); break; case 5: widget.setPreferenceForKey("images/r4.gif","logo"); widget.setPreferenceForKey("49716","ID"); widget.setPreferenceForKey("LW","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/radio4/realplayer/media/lwg2.rpm","url"); changeImage(); break; case 6: widget.setPreferenceForKey("images/r5.gif","logo"); widget.setPreferenceForKey("49701","ID"); widget.setPreferenceForKey("UK","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/fivelive/live/surestream.rpm","url"); changeImage(); break; case 7: widget.setPreferenceForKey("images/r5.gif","logo"); widget.setPreferenceForKey("49701","ID"); widget.setPreferenceForKey("INTL","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/fivelive/live/surestream_int.rpm","url"); changeImage(); break; case 8: widget.setPreferenceForKey("images/6music.gif","logo"); widget.setPreferenceForKey("49707","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/6music/ram/dsatg2.rpm","url"); changeImage(); break; case 9: widget.setPreferenceForKey("images/bbc7.gif","logo"); widget.setPreferenceForKey("18112","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/bbc7/realplayer/dsatg2.rpm","url"); changeImage(); break; case 10: widget.setPreferenceForKey("images/1xtra.gif","logo"); widget.setPreferenceForKey("49706","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/1xtra/realmedia/1xtralive.rpm","url"); changeImage(); break; case 11: widget.setPreferenceForKey("images/5livextra.gif","logo"); widget.setPreferenceForKey("49704","ID"); widget.setPreferenceForKey("UK","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/fivelive/live/surestream_sportsextra.rpm","url"); changeImage(); break; case 12: widget.setPreferenceForKey("images/5livextra.gif","logo"); widget.setPreferenceForKey("49704","ID"); widget.setPreferenceForKey("INTL","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/fivelive/live/surestream_sportsextra_int.rpm","url"); changeImage(); break; case 13: widget.setPreferenceForKey("images/asian.gif","logo"); widget.setPreferenceForKey("2030","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/asiannetwork/rams/asiannetwork.rpm","url"); changeImage(); break; case 14: widget.setPreferenceForKey("images/ws.gif","logo"); widget.setPreferenceForKey("49702","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/worldservice/ram/live_infent.rpm","url"); changeImage(); break; case 15: widget.setPreferenceForKey("images/scotland.gif","logo"); widget.setPreferenceForKey("61441","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/scotland/radioscotland/media/radioscotland.rpm","url"); changeImage(); break; case 16: widget.setPreferenceForKey("images/wales.gif","logo"); widget.setPreferenceForKey("50232","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/wales/live/rwg2.rpm","url"); changeImage(); break; case 17: widget.setPreferenceForKey("images/ulster.gif","logo"); widget.setPreferenceForKey("49977","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/northernireland/realmedia/ru-live.rpm","url"); changeImage(); break; case 18: widget.setPreferenceForKey("images/cymru.gif","logo"); widget.setPreferenceForKey("49991","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/cymru/live/rc-live.ram","url"); changeImage(); break; case 19: widget.setPreferenceForKey("images/foyle.gif","logo"); widget.setPreferenceForKey("50233","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/ni/realmedia/rf-live.ram","url"); changeImage(); break; case 20: widget.setPreferenceForKey("images/ng.gif","logo"); widget.setPreferenceForKey("49974","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/scotland/alba/media/live/radio_ng.ram","url"); changeImage(); break; case 21: widget.setPreferenceForKey("Berkshire","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52529","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/radioberkshire.ram","url"); changeImage(); break; case 22: widget.setPreferenceForKey("Bristol","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50226","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/bristol.ram","url"); changeImage(); break; case 23: widget.setPreferenceForKey("Cambridgeshire","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50225","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/cambridgeshire.ram","url"); changeImage(); break; case 24: widget.setPreferenceForKey("Cleveland","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52020","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/cleveland.ram","url"); changeImage(); break; case 25: widget.setPreferenceForKey("Cornwall","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50994","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/cornwall.ram","url"); changeImage(); break; case 26: widget.setPreferenceForKey("Coventry & Warks","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51251","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/coventryandwarks.ram","url"); changeImage(); break; case 27: widget.setPreferenceForKey("Cumbria","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50996","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/cumbria.ram","url"); changeImage(); break; case 28: widget.setPreferenceForKey("Derby","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50739","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/derby.ram","url"); changeImage(); break; case 29: widget.setPreferenceForKey("Devon","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51250","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/devon.ram","url"); changeImage(); break; case 30: widget.setPreferenceForKey("Essex","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51249","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/essex.ram","url"); changeImage(); break; case 31: widget.setPreferenceForKey("Gloucestershire","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52018","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/gloucestershire.ram","url"); changeImage(); break; case 32: widget.setPreferenceForKey("Guernsey","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50487","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/guernsey.ram","url"); changeImage(); break; case 33: widget.setPreferenceForKey("Hereford & Worcester","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50483","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/herefordandworcester.ram","url"); changeImage(); break; case 34: widget.setPreferenceForKey("Humberside","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52532","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/humberside.ram","url"); changeImage(); break; case 35: widget.setPreferenceForKey("Jersey","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50231","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/jersey.ram","url"); changeImage(); break; case 36: widget.setPreferenceForKey("Kent","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50737","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/kent.ram","url"); changeImage(); break; case 37: widget.setPreferenceForKey("Lancashire","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50484","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/lancashire.ram","url"); changeImage(); break; case 38: widget.setPreferenceForKey("Leeds","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52788","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/leeds.ram","url"); changeImage(); break; case 39: widget.setPreferenceForKey("Leicester","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51507","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/leicester.ram","url"); changeImage(); break; case 40: widget.setPreferenceForKey("Lincolnshire","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52019","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/lincolnshire.ram","url"); changeImage(); break; case 41: widget.setPreferenceForKey("London","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52273","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/london.ram","url"); changeImage(); break; case 42: widget.setPreferenceForKey("Manchester","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50228","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/manchester.ram","url"); changeImage(); break; case 43: widget.setPreferenceForKey("Merseyside","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50740","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/merseyside.ram","url"); changeImage(); break; case 44: widget.setPreferenceForKey("Newcastle","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51764","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/newcastle.ram","url"); changeImage(); break; case 45: widget.setPreferenceForKey("Norfolk","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51505","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/norfolk.ram","url"); changeImage(); break; case 46: widget.setPreferenceForKey("Northampton","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("53041","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/northampton.ram","url"); changeImage(); break; case 47: widget.setPreferenceForKey("Nottingham","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50995","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/nottingham.ram","url"); changeImage(); break; case 48: widget.setPreferenceForKey("Oxford","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52017","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/radiooxford.ram","url"); changeImage(); break; case 49: widget.setPreferenceForKey("Sheffield","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("53044","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/sheffield.ram","url"); changeImage(); break; case 50: widget.setPreferenceForKey("Shropshire","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51763","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/shropshire.ram","url"); changeImage(); break; case 51: widget.setPreferenceForKey("Solent","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51506","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/solent.ram","url"); changeImage(); break; case 52: widget.setPreferenceForKey("Somerset","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("40961","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/somerset.ram","url"); changeImage(); break; case 53: widget.setPreferenceForKey("Southern Counties","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52768","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/southerncounties.ram","url"); changeImage(); break; case 54: widget.setPreferenceForKey("Stoke","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52275","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/stoke.ram","url"); changeImage(); break; case 55: widget.setPreferenceForKey("Suffolk","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51761","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/suffolk.ram","url"); changeImage(); break; case 56: widget.setPreferenceForKey("Swindon","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("51762","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/swindon.ram","url"); changeImage(); break; case 57: widget.setPreferenceForKey("Three Counties","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50993","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/threecounties.ram","url"); changeImage(); break; case 58: widget.setPreferenceForKey("Wiltshire","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52274","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/wiltshire.ram","url"); changeImage(); break; case 59: widget.setPreferenceForKey("WM","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("50227","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/wm.ram","url"); changeImage(); break; case 60: widget.setPreferenceForKey("York","area"); widget.setPreferenceForKey("images/local.gif","logo"); widget.setPreferenceForKey("52276","ID"); widget.setPreferenceForKey("","band"); widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/york.ram","url"); changeImage(); break; } } } // changeImage() is called on loading and after preference changes. // It checks which images should be shown and changes the html to show it. // Also adds any additional info such as local radio area over the image, // if appropriate. function changeImage() { // read preferences and set variable for the elements var logo = widget.preferenceForKey("logo"); var localName = widget.preferenceForKey("area"); var stationBand = widget.preferenceForKey("band"); var logoImage = document.getElementById("logo"); var localContainer = document.getElementById("local_name_container"); var bandContainer = document.getElementById("band_container"); // not relevant after the first time this version is run, but we have an // all-new efficient way of reselecting the appropriate station from the // popup based on preferences, which defaults to radio 1 if there's no pref // (first run). Checking for that preference here helps us keep everything // in sync, without having to include a lot of old, messy code as well. var optionToSelect = widget.preferenceForKey("selection"); // check for valid preference if (optionToSelect && (optionToSelect.length>0) && logo && (logo.length>0)) // set the source of the image to the path which we just read from // preferences { logoImage.src=logo } // default to r1, as with all settings, if nothing's saved, or if there's // no "optionToSelect" (first run, or first run of new version) else { logoImage.src="images/r1.gif" }; // if it's the local radio station image if (logo=="images/local.gif") { // sets the text inside it to match the name of the station (set during // setPrefs()) localContainer.innerText = localName; // show the div which will contain the name of the local station localContainer.style.display="block"; // and hide the LW/FM/UK/INTL indicator as it's redundant here bandContainer.style.display="none"; } // otherwise, if there are multiple station bands/variants else if (stationBand && stationBand.length > 0) { // add the appropriate band/variant info bandContainer.innerText = stationBand; // hide the local info, it's redundant localContainer.style.display="none"; // show the band container bandContainer.style.display="block"; } else { // turn off both if neither needed localContainer.style.display="none"; bandContainer.style.display="none"; } } // changeStream() - formerly changeStreamUrl() - is called when the play button // is pressed, or after preferences are changed. It checks which stream should // be playing, and then composes some html which embeds the real player plugin // in the widget to stream the audio. Then the html is pasted into the widget. function changeStream() { // read prefs and locate the span to paste into // (html is pasted into the document to place the RealPlayer plugin with the // correct stream url) var playerContainer = document.getElementById("radio"); var station = widget.preferenceForKey("url"); // as before, including this pref check ensures predictable behaviour after // upgrading from an earlier version var optionToSelect = widget.preferenceForKey("selection"); // check that prefs are valid if (station && (station.length > 0) && optionToSelect && (optionToSelect.length > 0)) { // this section composes the html in the form an embed tag for // RealPlayer, which is provided with a custom source url which we read // from preferences earlier var html = '<embed src="'+station+'" type="audio/x-pn-realaudio-plugin" autostart="true" height="0" width="0" console="one" nojava="true" id="rp" scriptcallbacks="OnPlayStateChange" />'; } // if there aren't valid preferences, we deault to radio 1 again else { // same html as before, but this time the src url is fixed at radio 1 var html = '<embed src="http://www.bbc.co.uk/radio1/realaudio/media/r1live.rpm" type="audio/x-pn-realaudio-plugin" autostart="true" height="0" width="0" console="one" nojava="true" id="rp" scriptcallbacks="OnPlayStateChange" />'; } // change the inner html of the radio span to the html we just composed playerContainer.innerHTML = html; // set the player to the volume the slider is showing changeVolume(); // ensures that RealPlayer plugin suppresses any errors, avoiding ugly // dialogue boxes document.getElementById("rp").SetWantErrors(true); } /*****************************************/ // HANDLING PLAYER INTERFACE MOUSE EVENTS /*****************************************/ // Called when the mouse goes over the div ID "whats_on", to fade the link in function whatsOnOver(e) { // Set our flag to true - the link should be showing shouldShowWhatsOn=true; // check that the mouse isn't down (ie, that the user isn't playing with // buttons), and that the progress indicator isn't runnng. Showing the // "What's On?" link in either case would be inappropriate. // Because shouldShowWhatsOn=true is outside of this "if" clause, we can // check to see if it's true after the user releases the mouse / the // indicator stops, and if appropriate fade the link in. if (theCurrentMouseDownButton==null && progressIndicatorTimer==null) { // This next section is all about the new(ish) AppleAnimator // See the developer documentation, it has better explanations than I // can give here // value to start at - in this case, 0, as we're starting from 0 opacity var from = 0; // if the animator is running, in whichever direction if(null != whatsOnAnimator) { // stops it whatsOnAnimator.stop(); // sets from as the current value instead from = whatsOnAnimator.animations[0].now; } // if the shift key is down, the multiplier is 10, if not, 1 var multiplier = e.shiftKey ? 10 : 1; // upshot of this is, increasing values 0 -> 1 get passed to whatsOnHandler whatsOnAnimator = new AppleAnimator(500*multiplier, 13, from, 1, whatsOnHandler); // starts it all off! whatsOnAnimator.start(); } } // Does the opposite - fades the link out on mouseout function whatsOnOut(e) { // we shouldn't be showing the link, set the flag accordingly shouldShowWhatsOn=false; // if the mouse isn't down having clicked on a button (this is set in the // button handlers); this link is governed by those handlers too, so // basically, if you click and drag away the link won't fade. Sweet huh? // It'll fade after you let go, though, because we check to see if // shouldShowWhatsOn is false (notice how that's outside of this "if"? ;) if (theCurrentMouseDownButton==null) { // again, setup; this time starting at 1 (opaque) var from = 1; // if it's running already, in either direction if(null != whatsOnAnimator) { // stop it whatsOnAnimator.stop(); // start at current value instead from = whatsOnAnimator.animations[0].now; } // if the shift key is down, the multiplier is 10, if not, 1 var multiplier = e.shiftKey ? 10 : 1; // pass decreasing values to the whatsOnHandler whatsOnAnimator = new AppleAnimator(500*multiplier, 13, from, 0, whatsOnHandler); // go! whatsOnAnimator.start(); } } // Here's the magic - apply the values passed to whatsOnHandler as // opacity changes to the link function whatsOnHandler(animation, current, from, to) { arrow.style.opacity = current; whats_on_text.style.opacity = current; // this makes the bezel fade in to a maximum of 0.65 opacity, but still // taking the same time overall (so it's at a slower rate) bezel.style.opacity = current*0.65; } // called when the mouse clicks on certain objects. // we get passed the reference of the object on which the mouse went down, and // whether it was the text link or the arrow (different behavoiur, if the arrow // was clicked we make it glow slightly) function theMouseDownHandler(event, div, clickedElement) { // loads each image we could use into a cariable, so that they're cached // and won't flicker when we swap them in var img = new Image; img.src = "images/arrow_clicked.png"; img.src = "images/arrow.png"; img.src = "images/play_clicked.png"; img.src = "images/play.png"; img.src = "images/pause_clicked.png"; img.src = "images/pause.png"; img.src = "images/knob_clicked.png"; img.src = "images/knob.png"; // passes the clickedElement (arrow/text) to the global variable // wasClickedElement so the other functions can use it wasClickedElement=clickedElement; // if we clicked on the play button if (wasClickedElement=="play") { // change to the clicked play button image document.getElementById("play").src="images/play_clicked.png"; } // if we clicked on the pause button if (wasClickedElement=="pause") { // change to the clicked play button image document.getElementById("pause").src="images/pause_clicked.png"; } // if we clicked on the arrow button else if (wasClickedElement=="arrow") { // change to the clicked arrow image document.getElementById("arrow").src="images/arrow_clicked.png"; } // if it's the volume slider/knob that was clicked else if (wasClickedElement=="knob" || wasClickedElement=="slider") { // change to the clicked knob image document.getElementById("knob").src="images/knob_clicked.png"; // Save starting positions of cursor and element. cursorStartX = event.clientX; knobStartLeft = parseInt(document.getElementById("knob").style.left, 10) } // if it's the volume slider, move the knob to point of click if (wasClickedElement=="slider") { // allowing for the size of the knob (we want it centred over our click, // but we position it using the left hand side) move it all the way to // the left if user clicked on the left boundary (global, set at start) if ((cursorStartX-6) < leftBound) { var skipKnobToHere = leftBound; } // same as above, but for right boundary (also set at start) else if ((cursorStartX-6) > rightBound) { var skipKnobToHere = rightBound; } // otherwise, skip the knob to where we clicked; again, allowing for the // size & the fact that we want to centre the knob over our click else { var skipKnobToHere = (cursorStartX-6); } // do the actual moving via setting the CSS property document.getElementById("knob").style.left = skipKnobToHere + "px"; // reset the knob's starting position to the new position, in case // we do some dragging after the click // Are you impressed that this is allowed? ;) I'm quite proud of this! knobStartLeft = parseInt(document.getElementById("knob").style.left, 10) // calls the function to change the volume, based on the new position changeVolume(); } // if we didn't click on the text if (wasClickedElement!="text") { // force the cursor to an arrow, in case we mouseover the text section // of "what's on?" - the cursor turning into a hand during drag would // look funny document.body.style.cursor = "default"; } // add listeners, to trigger handlers if the mouse does anything document.addEventListener("mousemove", theMouseMoveHandler, true); document.addEventListener("mouseup", theMouseUpHandler, true); div.addEventListener("mouseover", theMouseOverHandler, true); div.addEventListener("mouseout", theMouseOutHandler, true); // set it up so we know globally that the mouse is down, and where div.arrowInside = true; theCurrentMouseDownButton = div; // I dunno, but Apple do this in their button handlers (from which all these // are derived) event.stopPropagation(); event.preventDefault(); } // when the mouse moves after mousedown, this is called function theMouseMoveHandler (event) { // if we clicked on the knob or the slider; ie, we're in a drag if (wasClickedElement=="knob" || wasClickedElement=="slider") { // calculate the knob position based on its start point and how much // the cursor has moved var newPosition = (knobStartLeft + event.clientX - cursorStartX); // if that's within the slider boundaries, move there if (newPosition > leftBound && newPosition < rightBound) { document.getElementById("knob").style.left = newPosition + "px"; } // if it's beyond the left boundary (set globally, at start) else if (newPosition < leftBound) { // just skip to the left boundary instead document.getElementById("knob").style.left = leftBound + "px"; } // if it's beyond the right boundary (again, set globally, at start) else if (newPosition > rightBound) { // skip to the right boundary document.getElementById("knob").style.left = rightBound + "px"; } // calls changeVolume() to update the volume based on these moves changeVolume(); } // like I said, Apple use these event.stopPropagation(); event.preventDefault(); } // this is called if the mouse goes back over the element - obviously, we must // already have had a mouseout event for this to be being called! function theMouseOverHandler (event) { // if the mouse button's still down, and the original click was on the arrow // or the button (not the knob or slider, we don't let the knob change // back from blue till the mouse button is released, so there's no point // in calling them here again) if (theCurrentMouseDownButton && wasClickedElement=="arrow") { // change to the clicked images document.getElementById("arrow").src="images/arrow_clicked.png"; } else if (theCurrentMouseDownButton && wasClickedElement=="play") { // change to the clicked images document.getElementById("play").src="images/play_clicked.png"; } else if (theCurrentMouseDownButton && wasClickedElement=="pause") { // change to the clicked images document.getElementById("pause").src="images/pause_clicked.png"; } // set variable to show the mouse is currently over the element clicked on theCurrentMouseDownButton.arrowInside = true; // getting bored of saying "I don't know" event.stopPropagation(); event.preventDefault(); } // called if the mouse moves away from the element clicked on function theMouseOutHandler (event) { // if the button's still down, and the click was on the arrow if (theCurrentMouseDownButton && wasClickedElement=="arrow") { // switch to the unclicked image, as the mouse isn't over it document.getElementById("arrow").src="images/arrow.png"; } // same for the play button else if (theCurrentMouseDownButton && wasClickedElement=="play") { // switch to the unclicked image, as the mouse isn't over it document.getElementById("play").src="images/play.png"; } // and the pause button else if (theCurrentMouseDownButton && wasClickedElement=="pause") { // switch to the unclicked image, as the mouse isn't over it document.getElementById("pause").src="images/pause.png"; } // set variable to indicate the mouse is outside the element clicked on theCurrentMouseDownButton.arrowInside = false; // je ne sais pas event.stopPropagation(); event.preventDefault(); } // when the button is released, this is called - the business part of all this function theMouseUpHandler (event) { // here we chance any image elements back to the unclicked form if (theCurrentMouseDownButton && wasClickedElement=="arrow") { // put the unclicked images back, the mouse is up! document.getElementById("arrow").src="images/arrow.png"; } else if (theCurrentMouseDownButton && wasClickedElement=="play") { // put the unclicked images back, the mouse is up! document.getElementById("play").src="images/play.png"; } else if (theCurrentMouseDownButton && wasClickedElement=="pause") { // put the unclicked images back, the mouse is up! document.getElementById("pause").src="images/pause.png"; } else if (theCurrentMouseDownButton && (wasClickedElement=="knob" || wasClickedElement=="slider")) { // put the unclicked images back, the mouse is up! document.getElementById("knob").src="images/knob.png"; } // callback to the client, clean up document.removeEventListener("mousemove", theMouseMoveHandler, true); document.removeEventListener("mouseup", theMouseUpHandler, true); if (theCurrentMouseDownButton) { theCurrentMouseDownButton.removeEventListener("mouseover", theMouseOverHandler, true); theCurrentMouseDownButton.removeEventListener("mouseout", theMouseOutHandler, true); } // Quintus est puer Romanus event.stopPropagation(); event.preventDefault(); // IF the button was depressed over the text/arrow, and IF it's currently // still over it, call a function if (theCurrentMouseDownButton && theCurrentMouseDownButton.arrowInside) { if (wasClickedElement=="arrow" || wasClickedElement=="text") { // show the what's on page openWhatsOnPage(); } // if it was the play button instead, call another function else if (wasClickedElement=="play") { // this places the plugin (unless audio is already playing) playClicked(event); } // if it was the pause button instead, call another function else if (wasClickedElement=="pause") { // this removes the plugin pauseClicked(); } } // reset the variable, as the button's been released theCurrentMouseDownButton = null; // restore the cursor to normal behaviour document.body.style.cursor = "auto"; // Called whether we visit the link or not, if we clicked on it. // Check to see if the link should be visible. We suppressed its auto-hide // while the mouse was still down (remember that check to see the state // of theCurrentMouseDownButton in the whatsOnOut() function?) - and just // set this flag as false to remind us to do it later. // So now we need to hide it again if it shouldn't be showing. if (shouldShowWhatsOn==false && (wasClickedElement=="arrow" || wasClickedElement=="text")) { // calls the fade out whatsOnOut(event); } // Likewise, if we didn't click on "what's on" and in the meantime we've // moused over it, fade the link in if (shouldShowWhatsOn==true && (wasClickedElement=="play" || wasClickedElement=="knob" || wasClickedElement=="slider")) { // calls the fade in whatsOnOver(event); } } // openWhatsOnPage() tells the widget to open the appropriate schedule in // a browser window function openWhatsOnPage() { // read the preference for the "service_id", the station identifier // that goes in the link to give us the correct schedule. // BBC-defined, goodness knows on what basis var serviceId = widget.preferenceForKey("ID"); // check for a pref if (serviceId && (serviceId.length > 0)) { // open the link. Notice the serviceId variable getting spliced in there widget.openURL('http://www.bbc.co.uk/cgi-perl/whatson/search/daylist.cgi?service_id='+serviceId+'&DAY=Today'); } else { // defaults to radio 1, as do all prefs widget.openURL('http://www.bbc.co.uk/cgi-perl/whatson/search/daylist.cgi?service_id=49697&DAY=Today'); } } // called when the play button is clicked (from theMouseUphandler), places the // plugin function playClicked(e) { // start the progress indicator running (so long as it's // currently stopped, otherwise we'd get it going double // speed!) if (progressIndicatorTimer==null) { startProgressIndicator('large'); } // hide the play button, show the pause button document.getElementById("play").style.display="none"; document.getElementById("pause").style.display="block"; // pinched this idea from the BBC ListenLive widget; hope Duncan Ponting // doesn't mind. It's a great trick! // sniff for the Real Player plugin; first set up a flag as false var pluginHere = false; // loop through all the plugins for (var i=0;i < navigator.plugins.length; i++) { // if the current one's called "RealPlayer Plugin" if (navigator.plugins[i].name.indexOf('RealPlayer')!=(-1)) { // set our flag to true pluginHere = true; // break the loop, we've found it break; } } // if the plugin's not installed if (!pluginHere) { // show a status message, with link showStatus(e,'<a href=javascript:widget.openURL("http://www.real.com/R/RC.021705realhome_1_2_2_1_1_3.ecomm...R/realguide.real.com/r/https_.html?url=order.real.com%2fpt%2forder.html%3fppath%3dcpmacpl060204a%26country%3dUS%26language%3dEN%26opage%3drealhome%26src%3d021705realhome_1_2_2_1_1_3")>RealPlayer plugin not found. Click text to download. (Widget will need to be restarted)</a>',15); // pretend pause is clicked, so we stop the indicator and reset the // button images pauseClicked(); } // if the plugin's installed // use xmlhttprequest to test for internet connection - if we can complete // a connection to the BBC radio player page we proceed and place the player // (if we place it without a connection we could get a crash later!) else { // set up the request var xmlhttp = new XMLHttpRequest(); // location et al xmlhttp.open("HEAD", "http://www.bbc.co.uk/radio/index.shtml",true); // every time the state of the request changes // run the following code xmlhttp.onreadystatechange=function() { // if the connection request is complete if (xmlhttp.readyState==4) { // and if the request was successful if (xmlhttp.status==200) { // place an instance of the plugin, change the volume, // change the play button to a pause button changeStream(); } // if unsuccessful else { // calls a function to show a "no connection" warning showStatus(e,"You don't appear to be connected to the internet. Please check your connection and try again.", 10); // pretend pause is clicked, so we stop the indicator and // reset the button images pauseClicked(); } } } // send nothing xmlhttp.send(null); } } // Called when pause is clicked, to remove the plugin and stop audio function pauseClicked() { // stop the progress indicator, in case it was running stopProgressIndicator(); // resets the inner html of the span holding the plugin document.getElementById("radio").innerHTML=""; // hide the pause button, shaow the play button document.getElementById("pause").style.display = "none"; document.getElementById("play").style.display = "block"; } // show a status message function showStatus(e, message, duration) { // pass the event to a global variable (we can't pass it on to hideStatus() // because we call it via setTimeout(), which doesn't support that, so we // have to make it global) globalStatusEvent=e; // set the inner html to the message we got passed document.getElementById("status").innerHTML=message; // show the status div (although it's transparent at the moment) document.getElementById("status").style.display="block"; // hide all the "What's On?" stuff, in case it shows document.getElementById("whats_on_text").style.display="none"; document.getElementById("bezel").style.display="none"; document.getElementById("arrow").style.display="none"; // setting up an AppleAnimator, to fade in the status message (speedily) // starting from 0 opacity var from=0; // if it's running if(null != statusAnimator) { // stop statusAnimator.stop(); // start from current value from = statusAnimator.animations[0].now; } // if the shift key is down, the multiplier is 10, if not, 1 var multiplier = e.shiftKey ? 10 : 1; // upshot of this is, increasing values get passed to statusFadeHandler statusAnimator = new AppleAnimator(500*multiplier, 13, from, 1, statusFadeHandler); // starts it all off! statusAnimator.start(); // call hideStatus after the amount of time we specified, to give plenty of // time to read it (we multiply by 1000 as 'duration' is specified in // seconds, but setTimeout uses thousanths of a second) duration = (duration*1000); setTimeout('hideStatus()',duration); } // hides the status div, called after 3 seconds from the showStatus() function function hideStatus() { // show all the "What's On?" stuff document.getElementById("whats_on_text").style.display="block"; document.getElementById("bezel").style.display="block"; document.getElementById("arrow").style.display="block"; // setting up an AppleAnimator, to fade in the status message speedily // starting from 1 opacity var from=1; // if it's running if(null != statusAnimator) { // stop statusAnimator.stop(); // start from current value from = statusAnimator.animations[0].now; } // if the shift key is down, the multiplier is 10, if not, 1 var multiplier = globalStatusEvent.shiftKey ? 10 : 1; // upshot of this is, decreasing values get passed to statusFadeHandler statusAnimator = new AppleAnimator(500*multiplier, 13, from, 0, statusFadeHandler); // starts it all off! statusAnimator.start(); } // handles the fading in and out of the status div function statusFadeHandler(animation, current, from, to) { // set the current opacity to what the Animator passes it document.getElementById("status").style.opacity=current; if (current==0) { // hide the status div (although it's transparent now) document.getElementById("status").style.display="none"; // reset message to nothing document.getElementById("status").innerHTML=""; } } // changes the volume when called (when placing a new plugin or when changing // the slider position), based on where the slider knob is function changeVolume() { // get the knob position var sliderPosition = parseInt(document.getElementById("knob").style.left, 10); // the start of the slider is 73 pixels in from the left, so deduct those; // also the slider lingth is 33, so times by 3 to get a value out of 99 // (so close!) - RealPlayer sets volume as a percentage, you see var newVolume = (sliderPosition-73)*3 // if there's a plugin instance currently placed if (document.getElementById("rp")) { // tell it to set the volume to the new value document.getElementById("rp").SetVolume(newVolume); } // if we're in Dashboard (notice this isn't a nested "if", so this is // regardless of whether we just changed an existing plugin's volume or not) if (window.widget) { // save this as a preference (we'll use it to determine where the slider // should be on next launch) widget.setPreferenceForKey(newVolume,"Volume"); } } // called at launch, to position the slider according to the volume level we've // got saved in preferences function restoreVolume() { // if we're in Dashboard if (window.widget) { // retrieve the preference var newVolume = widget.preferenceForKey("Volume"); if (newVolume) { // divide by 3 to get a value out of 33 (the slider length); then // add 73 to this (the slider offset from the left margin) so we get // an absolute position for the knob var sliderPosition = (newVolume/3)+73; // set the knob to this position document.getElementById("knob").style.left = sliderPosition; } } } /****************************/ // CALLBACK FROM THE PLUGIN /****************************/ // called when the plugin changes state - eg. starts playing, or stops; is // passed the old and new states. function OnPlayStateChange(oldState,newState) { // if the plugin starts playing if (newState==3) { // stops the indicator stopProgressIndicator(); } // otherwise, if the player stops (accidentally) else if (newState==0) { // act as though the pause button was clicked - tidy things up by // killing the progress indicator (if it's going), removing the plugin, // showing the play button again pauseClicked(); } } // Called whenever plugin buffering begins - is passed the type of buffering // (startup/network congestion etc), and the percent complete. function OnBuffering(flag,percent) { // if the buffering results from anything other than startup (we already // have some code handling that scenario, and it's more responsive than // this) if (flag!=0) { // start the progress indicator, for visual feedback startProgressIndicator('large'); } } /*********************************/ // HIDING AND SHOWING PREFERENCES /*********************************/ // showPrefs() is called when the preferences flipper is clicked upon. It // freezes the front of the widget, hides the front div, unhides the back div, // and then flips the widget over. function showPrefs() { // get the front and back divs var front = document.getElementById("front"); var back = document.getElementById("back"); // check to see what the latest version is; at intervals of 2 days, this // function will check online, otherwise it uses a local cache of the last // known value. checkForUpdate(); // if we're in Dashboard if (window.widget) // We're going to the back where preferences may be changed, so now's a good // time to check the radio station playing by getting its url from the // preferences - this will come in useful later! // NB. Originally used ID, but this fails on UK/INTL streams as they have // the same BBC ID. preferenceBefore = widget.preferenceForKey("url"); // freezes the widget so that you can change it without the user noticing widget.prepareForTransition("ToBack"); // hide the front front.style.display="none"; // show the back back.style.display="block"; // and flip the widget over if (window.widget) { setTimeout ('widget.performTransition();', 0); } // clean up the front side - hide the circle behind the info button document.getElementById('fliprollie').style.display = 'none'; } // hidePrefs() is called by the done button on the back side of the widget. It // performs the opposite transition as showPrefs() does. function hidePrefs() { // get the front and back var front = document.getElementById("front"); var back = document.getElementById("back"); // if we're in Dashboard if (window.widget) // now we're going back to the front, so any changes have been made - we // get the radio station just selected, by getting its url from preferences preferenceAfter = widget.preferenceForKey("url"); // if a new station has been selected, we change the audio stream playing; // otherwise we leave it alone so that playback isn't interrupted if (preferenceBefore!=preferenceAfter) { // pretend we clicked the play button, to start the audio playClicked(event); } // freezes the widget and prepares it for the flip back to the front widget.prepareForTransition("ToFront"); // hide the back back.style.display="none"; // show the front front.style.display="block"; // and flip the widget back to the front if (window.widget) { setTimeout ('widget.performTransition();', 0); } }